#pragma one

#include <iostream>
#include <mutex>
#include <fstream>
#include <assert.h>
#include <jsoncpp/json/json.h>

#include "../comm/log.hpp"
#include "../comm/util.hpp"
// #include "oj_model1.hpp"
#include "oj_model2.hpp"
#include "oj_view.hpp"

namespace ns_control
{
	using namespace std;
	using namespace ns_log;
	using namespace ns_util;
	using namespace ns_model;
	using namespace ns_view;
	using namespace httplib;

	// 提供服务的主机
	class Machine
	{
	public:
		string _ip;		// 编译服务的ip
		int _port;		// 编译服务的port
		uint64_t _load; // 编译服务的负载
		mutex *_mutex;	// mutex禁止拷贝的，使用指针
	public:
		Machine() : _ip(""), _port(0), _load(0), _mutex(nullptr)
		{
		}
		~Machine() {}

	public:
		// 增加负载
		void IncLoad()
		{
			if (_mutex)
				_mutex->lock();
			++_load;
			if (_mutex)
				_mutex->unlock();
		}
		// 减少负载
		void DecLoad()
		{
			if (_mutex)
				_mutex->lock();
			--_load;
			if (_mutex)
				_mutex->unlock();
		}
		// 负载清除
		void ResetLoad()
		{
			if (_mutex)
				_mutex->lock();
			_load = 0;
			if (_mutex)
				_mutex->unlock();
		}
		// 初始负载
		uint64_t Load()
		{
			uint64_t load = 0;
			if (_mutex)
				_mutex->lock();
			load = _load;
			if (_mutex)
				_mutex->unlock();
			return load;
		}
	};

	const string service_machine = "./conf/service_machine.conf";
	// 负载均衡模块
	class LoadBlance
	{
	private:
		// 可以给我们提供编译服务的所有的主机
		// 每一台主机都有自己的下标，充当当前主机的id
		vector<Machine> _machines;
		vector<int> _online_mc;	 // 所有在线的主机id
		vector<int> _offline_mc; // 所有离线的主机id
		mutex _mutex;

	public:
		LoadBlance() 
		{
			assert(LoadConf(service_machine));
			LOG(INFO) << "加载" << service_machine << "成功" << "\n";
		}
		~LoadBlance() {}

	public:
		// 加载配置
		bool LoadConf(const string &mc_conf)
		{
			ifstream in(mc_conf);
			if(!in.is_open())
			{
				LOG(FATAL) <<  " 加载: " << mc_conf <<"失败"<<"\n";
				return false;
			}

			string line;
			while(getline(in,line))
			{
				//切分出主机和端口号
				vector<string> tokens;
				StringUtil::SplitStr(line,&tokens,":");
				if(tokens.size() != 2)
				{
					LOG(WARNING) << " 切分 " << line << " 失败" << "\n";
					continue;
				}
				Machine mc;
				mc._ip = tokens[0];
				mc._port = atoi(tokens[1].c_str());
				mc._load = 0;
				mc._mutex = new mutex();
				_online_mc.push_back(_machines.size());
				_machines.push_back(mc);
			}
			in.close();
			return true;
		}

		// 智能选择，优先选择负载小的主机
		bool SmartChioce(int *id,Machine **mc)
		{
			// 1. 使用选择好的主机(更新该主机的负载)
            // 2. 我们需要可能离线该主机
			_mutex.lock();
			// 负载均衡的算法
            // 1. 随机数+hash
            // 2. 轮询+hash
			int online_num = _online_mc.size();
			if(online_num == 0)
			{
				_mutex.unlock();
				LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看" << "\n";
				return false;
			}

			// 通过遍历的方式，找到所有负载最小的机器
			*id = _online_mc[0];
			*mc = &_machines[_online_mc[0]];
			uint64_t min_load = _machines[_online_mc[0]]._load;
			for(int i = 1;i < online_num;i++)
			{
				uint64_t load = _machines[_online_mc[i]]._load;
				if(min_load > load)
				{
					min_load = _machines[_online_mc[i]]._load;
					*id = _online_mc[i];
					*mc = &_machines[_online_mc[i]];
				}
			}
			_mutex.unlock();
			return true;
		}

		// 让主机上线
		void OnlineMachine()
		{
			_mutex.lock();
			_online_mc.insert(_online_mc.end(),_offline_mc.begin(),_offline_mc.end());
			_offline_mc.erase(_offline_mc.begin(),_offline_mc.end());
			_mutex.unlock();
			LOG(INFO) << "所有的主机上线!" << "\n";
		}

		// 让某台主机下线
		void OfflineMachine(int id)
		{
			_mutex.lock();
			for(auto iter = _online_mc.begin();iter != _online_mc.end();iter++)
			{
				if(*iter == id)
				{
					_machines[id].ResetLoad();
					_online_mc.erase(iter);
					_offline_mc.push_back(id);
					break;
				}
			}
			_mutex.unlock();
		}

		//for test
		void ShowMachines()
		{
			_mutex.lock();
			cout << "当前在线主机列表: ";
			for(auto &id : _online_mc)
			{
				cout << id << " ";
			}
			cout << endl;
			cout <<  "当前离线主机列表: ";
			for(auto &id : _offline_mc)
			{
				cout << id << " ";
			}
			cout << endl;
			_mutex.unlock();
		}
	};

	// 这是我们的核心业务逻辑的控制器
	class Control
	{
	private:
		Model _model; // 提供后台数据
		View _view;	  // 提供html渲染功能
		LoadBlance _load_blance;	// 负载均衡
	public:
		Control() {}
		~Control() {}

	public:
		//让服务主机上线
		void RecoveryMachine()
		{
			_load_blance.OnlineMachine();
		}

		// 根据题目数据构建网页
		//  html: 输出型参数
		bool AllQuestions(string *html)
		{
			bool ret = true;
			vector<struct Question> all;
			if (_model.GetAllQuestions(&all))
			{
				// 根据题目的number进行排序
				sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2)
					 { return (atoi(q1.number.c_str())) < (atoi(q2.number.c_str())); });
				// 获取题目信息成功，将所有的题目数据构建成网页
				_view.AllExpandHtml(all, html);
			}
			else
			{
				*html = "获取题目失败, 形成题目列表失败";
				ret = false;
			}

			return ret;
		}

		// 对题目详情页进行渲染
		bool Question(const string &number, string *html)
		{
			bool ret = true;
			struct Question q;
			if (_model.GetOneQuestion(number, &q))
			{
				// 获取指定题目信息成功，将所有的题目数据构建成网页
				_view.OneExpandHtml(q, html);
			}
			else
			{
				*html = "指定题目: " + number + " 不存在!";
				ret = false;
			}

			return ret;
		}

		// 判断
		void Judge(const string& number,const string in_json,string *out_json)
		{
			// 0. 根据题目编号，直接拿到对应的题目细节
			struct Question q;
			_model.GetOneQuestion(number,&q);

			//1. in_json进行反序列化，得到题目的id，得到用户提交源代码，input
			Json::Reader reader;
			Json::Value in_value;
			reader.parse(in_json,in_value);
			string code = in_value["code"].asString();

			//2. 重新拼接用户代码+测试用例代码，形成新的代码
			Json::Value compile_value;
			compile_value["input"] = in_value["input"].asString();
			compile_value["code"] = code +"\n" + q.tail;
			compile_value["cpu_limit"] = q.cpu_limit;
			compile_value["mem_limit"] = q.mem_limit;
			Json::FastWriter writer;
			string compile_str = writer.write(compile_value);

			//// 3. 选择负载最低的主机(差错处理)
            // 规则: 一直选择，直到主机可用，否则，就是全部挂掉
			while(true)
			{
				int id = 0;
				Machine *mc = nullptr;
				if(!_load_blance.SmartChioce(&id,&mc))
				{
					break;
				}
				// 4. 然后发起http请求，得到结果
				Client cli(mc->_ip,mc->_port);
				mc->IncLoad();
				// LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << mc->_ip << ":" << mc->_port << " 当前主机的负载是: " << mc->Load() << "\n";
				if(auto resp = cli.Post("/compile_and_run",compile_str,"application/json;charset=utf-8"))
				{
					// 5. 将结果赋值给out_json
					if(resp->status == 200)
					{
						*out_json = resp->body;
						mc->DecLoad();
						LOG(INFO) << "请求编译和运行服务成功..." << "\n";
						break;
					}
					mc->DecLoad();
				}
				else
				{
					//请求失败
					LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << mc->_ip << ":" << mc->_port << " 可能已经离线"<< "\n";
					_load_blance.OfflineMachine(id);
					_load_blance.ShowMachines(); //仅仅是为了用来调试
				}
			}
		}
	};
}